// A workspace/stack memory manager



#ifndef _WS__HPP_
#define _WS__HPP_


#include "error.hpp"


namespace ws{



/** \brief A class that implements a temporary memory manager.

    It is common in several programming examples to need temporary memory for some
    operation. The most direct way to get this memory is to use the heap with
    operators like \code{new} and \code{delete} or library commands similar
    to \code{malloc} and \code{free}. This approach works fine but might have a
    poor performance and this performance might degrade due to memory fragmentation.
    This might usually not be a problem because memory managers have improved a lot.
    But when the maximum amount of temporary memory is approximately known beforehand,
    another approach is possible: allocate this memory beforehand and use a stack
    to manage this memory.

    The memory class provides an implementation of such a stack. It is meant to be
    used for uniform data types (but it is not be difficult to use it with
    any other type).

    Remember that this implementation is not thread safe. If a multithreading program
    is using this library, some modification might be necessary and/or one stack could
    be allocated per thread.

    If the stack is copied, no problems will arise. Using references, the different
    copies of the stack will be communicating throughout the stack's life.
    
 */
template<typename T>
class WorkSpace
{
private:
  int _SIZE;		///< Total size of the stack
  bool _mem_owner;	///< Is the object owner of the memory block?
  T * _data;		///< The actual memory block.
  T* _ref_ptr;		///< Pointer that indicates the top of the stack
  
public:
  /**\brief Creates a new stack

     This constructor actually creates a new stack. The reference pointer is set the
     beggining of the memory block and the stack pointer (data_ptr_) is a reference
     to this reference pointer (ref_ptr_).

     \param maxnum Maximum number of elements in the stack.
   */
  WorkSpace(int maxnum, int nt=1):_SIZE(maxnum*nt), _mem_owner(true),
    _data(new double[_SIZE]), _ref_ptr(_data){}
  WorkSpace(): _SIZE(0), _mem_owner(false), _data(0), _ref_ptr(0){}

  ~WorkSpace()
  {
    if (_mem_owner)
      {
	delete [] _data;
      }
  }

  /** \brief Copy constructor.

     This copy consctructor just sets a link to the original stack. It doesn't create
     a new copy. The \code{data_ptr_} is actually a reference to the top of the stack.
     If the stack is new, it will point to \code{ref_ptr_}. If the stack is a copy (thus
     this constructor), it will be a reference to the original reference pointer.

  */
  WorkSpace(const WorkSpace<T> &m): _SIZE(m._SIZE), _mem_owner(false) ,
				    _data(m._data),  _ref_ptr(m._ref_ptr) {}

  void create(int maxnum)
  {
    _SIZE = maxnum;
    if (_data) delete [] _data;
    _data = new double[_SIZE];
    _mem_owner = true;
    _ref_ptr = _data;
  }
  
  /** Allocates N objects. If N is negative, an exception is thrown. If N is too large,
      (it doesn't fit on the stack) an exception will bre thrown.
   */
  T *alloc(const int N)
  {
    
    if (N < 0)
      throw StackBlow("It is not possible to allocate negative number of elements");
    
    T *ptr = _ref_ptr;
    
    _ref_ptr = _ref_ptr + N;
      
    if (_ref_ptr > (_data + _SIZE))
      {
	_ref_ptr = _ref_ptr - N;
	throw StackBlow("Could not allocate more objects on stack");
      }
  
  return ptr;
  }

  /** Allocates N objects. If N is negative, an exception is thrown. If N is too large,
      (it doesn't fit on the stack) an exception will bre thrown.
   */
  T *operator()(const int N)
  {
    return alloc(N);
  }

  /** Frees memory previously allocated. It actually frees all the memory up to the pointer
      \code{ptr}. \code{ptr} must be within the stack memory block.
   */
  void free(T *ptr)
  {
    if (ptr < &_data[0])
      throw StackBlow("Too many objects to deallocate");
    else if (ptr > (&_data[0] + _SIZE))
      throw StackBlow("Pointer is not in stack range");
    _ref_ptr = ptr;
  }

  /** Reinitializes the stack. A quick way to "delete" everything from the stack.
   */
  void clear()
  {
    _ref_ptr = _data;
  }

  /** Returns a pointer to the base of the stack. 
   */
  T* data(){ return _data; }

 
};


}

#endif // _WS__HPP_

